// Copyright 2017 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#pragma once

#ifndef __Fuchsia__
#error "Fuchsia-only header"
#endif

#include <stdint.h>

#include <fbl/intrusive_double_list.h>
#include <fbl/unique_ptr.h>
#include <fs/vfs.h>
#include <fs/vnode.h>
#include <fuchsia/io/c/fidl.h>
#include <lib/async/cpp/wait.h>
#include <lib/zx/event.h>
#include <zircon/fidl.h>

namespace fs {

constexpr zx_signals_t kLocalTeardownSignal = ZX_USER_SIGNAL_1;

// A one-way message which may be emitted by the server without an
// accompanying request. Optionally used as a part of the Open handshake.
struct OnOpenMsg {
  fuchsia_io_NodeOnOpenEvent primary;
  fuchsia_io_NodeInfo extra;
};

// Connection represents an open connection to a Vnode (the server-side component
// of a file descriptor). Connections will be managed in a |fbl::DoublyLinkedList|.
// The Vnode's methods will be invoked in response to FIDL protocol messages
// received over the channel.
//
// This class is thread-safe.
class Connection : public fbl::DoublyLinkedListable<fbl::unique_ptr<Connection>> {
 public:
  // Create a connection bound to a particular vnode.
  //
  // The VFS will be notified when remote side closes the connection.
  //
  // |vfs| is the VFS which is responsible for dispatching operations to the vnode.
  // |vnode| is the vnode which will handle I/O requests.
  // |channel| is the channel on which the FIDL protocol will be served.
  // |flags| are the file flags passed to |open()|, such as |ZX_FS_FLAG_VNODE_REF_ONLY|;
  //         they include rights granted to this connection, such as |ZX_FS_RIGHT_READABLE|.
  Connection(fs::Vfs* vfs, fbl::RefPtr<fs::Vnode> vnode, zx::channel channel, uint32_t flags);

  // Closes the connection.
  //
  // The connection must not be destroyed if its wait handler is running
  // concurrently on another thread.
  //
  // In practice, this means the connection must have already been remotely
  // closed, or it must be destroyed on the wait handler's dispatch thread
  // to prevent a race.
  virtual ~Connection();

  // Set a signal on the channel which causes it to be torn down and
  // closed asynchronously.
  void AsyncTeardown();

  // Explicitly tear down and close the connection synchronously.
  void SyncTeardown();

  // Begins waiting for messages on the channel.
  //
  // Must be called at most once in the lifetime of the connection.
  zx_status_t Serve();

  // Node Operations.
  zx_status_t NodeClone(uint32_t flags, zx_handle_t object);
  zx_status_t NodeClose(fidl_txn_t* txn);
  zx_status_t NodeDescribe(fidl_txn_t* txn);
  zx_status_t NodeSync(fidl_txn_t* txn);
  zx_status_t NodeGetAttr(fidl_txn_t* txn);
  zx_status_t NodeSetAttr(uint32_t flags, const fuchsia_io_NodeAttributes* attributes,
                          fidl_txn_t* txn);
  zx_status_t NodeIoctl(uint32_t opcode, uint64_t max_out, const zx_handle_t* handles_data,
                        size_t handles_count, const uint8_t* in_data, size_t in_count,
                        fidl_txn_t* txn);

  // File Operations.
  zx_status_t FileRead(uint64_t count, fidl_txn_t* txn);
  zx_status_t FileReadAt(uint64_t count, uint64_t offset, fidl_txn_t* txn);
  zx_status_t FileWrite(const uint8_t* data_data, size_t data_count, fidl_txn_t* txn);
  zx_status_t FileWriteAt(const uint8_t* data_data, size_t data_count, uint64_t offset,
                          fidl_txn_t* txn);
  zx_status_t FileSeek(int64_t offset, fuchsia_io_SeekOrigin start, fidl_txn_t* txn);
  zx_status_t FileTruncate(uint64_t length, fidl_txn_t* txn);
  zx_status_t FileGetFlags(fidl_txn_t* txn);
  zx_status_t FileSetFlags(uint32_t flags, fidl_txn_t* txn);
  zx_status_t FileGetBuffer(uint32_t flags, fidl_txn_t* txn);

  // Directory Operations.
  zx_status_t DirectoryOpen(uint32_t flags, uint32_t mode, const char* path_data, size_t path_size,
                            zx_handle_t object);
  zx_status_t DirectoryUnlink(const char* path_data, size_t path_size, fidl_txn_t* txn);
  zx_status_t DirectoryReadDirents(uint64_t max_out, fidl_txn_t* txn);
  zx_status_t DirectoryRewind(fidl_txn_t* txn);
  zx_status_t DirectoryGetToken(fidl_txn_t* txn);
  zx_status_t DirectoryRename(const char* src_data, size_t src_size, zx_handle_t dst_parent_token,
                              const char* dst_data, size_t dst_size, fidl_txn_t* txn);
  zx_status_t DirectoryLink(const char* src_data, size_t src_size, zx_handle_t dst_parent_token,
                            const char* dst_data, size_t dst_size, fidl_txn_t* txn);
  zx_status_t DirectoryWatch(uint32_t mask, uint32_t options, zx_handle_t watcher, fidl_txn_t* txn);

  // DirectoryAdmin Operations.
  zx_status_t DirectoryAdminMount(zx_handle_t remote, fidl_txn_t* txn);
  zx_status_t DirectoryAdminMountAndCreate(zx_handle_t remote, const char* name, size_t name_size,
                                           uint32_t flags, fidl_txn_t* txn);
  zx_status_t DirectoryAdminUnmount(fidl_txn_t* txn);
  zx_status_t DirectoryAdminUnmountNode(fidl_txn_t* txn);
  zx_status_t DirectoryAdminQueryFilesystem(fidl_txn_t* txn);
  zx_status_t DirectoryAdminGetDevicePath(fidl_txn_t* txn);

 protected:
  // Dispatches incoming FIDL messages which aren't recognized by
  // |HandleMessage|.
  //
  // Takes ownership of the FIDL message's handles.
  // The default implementation just closes these handles.
  //
  // This implementation may be overridden to support additional non-VFS
  // FIDL protocols.
  virtual zx_status_t HandleFsSpecificMessage(fidl_msg_t* msg, fidl_txn_t* txn);

  // Acquires the vnode so subclasses of Connection can cast and dispatch
  // the Vnode to more specific subclasses.
  fs::Vnode& GetVnode() const { return *vnode_.get(); }

 private:
  // Callback for when new signals arrive on the channel, which could be:
  // readable, peer closed, async teardown request, etc.
  void HandleSignals(async_dispatcher_t* dispatcher, async::WaitBase* wait, zx_status_t status,
                     const zx_packet_signal_t* signal);

  // Closes the connection and unregisters it from the VFS object.
  void Terminate(bool call_close);

  // Sends an explicit close message to the underlying vnode.
  // Only necessary if the handler has not returned ERR_DISPATCHER_DONE
  // and has been opened.
  void CallClose();

  // Dispatches incoming FIDL messages.
  //
  // By default, handles the Node, File, Directory and DirectoryAdmin
  // protocols, dispatching to |HandleFsSpecificMessage| if the ordinal is not recognized.
  zx_status_t HandleMessage(fidl_msg_t* msg, fidl_txn_t* txn);

  bool is_open() const { return wait_.object() != ZX_HANDLE_INVALID; }
  void set_closed() { wait_.set_object(ZX_HANDLE_INVALID); }

  fs::Vfs* const vfs_;
  fbl::RefPtr<fs::Vnode> vnode_;

  // Channel on which the connection is being served.
  zx::channel channel_;

  // Asynchronous wait for incoming messages.
  // The object field is |ZX_HANDLE_INVALID| when not actively waiting.
  async::WaitMethod<Connection, &Connection::HandleSignals> wait_;

  // Open flags such as |ZX_FS_RIGHT_READABLE|, |ZX_FS_FLAG_VNODE_REF_ONLY|, and other bits.
  // Permissions on the underlying Vnode are granted on a per-connection basis,
  // and expressed as a combination of ZX_FS_RIGHT flags.
  // Importantly, ZX_FS_RIGHT flags are hierarchical over Open/Clone. It is never allowed
  // to derive a Connection with more rights than the originating connection.
  uint32_t flags_;

  // Handle to event which allows client to refer to open vnodes in multi-path
  // operations (see: link, rename). Defaults to ZX_HANDLE_INVALID.
  // Validated on the server-side using cookies.
  zx::event token_{};

  // Directory cookie for readdir operations.
  fs::vdircookie_t dircookie_{};

  // Current seek offset.
  size_t offset_{};
};

}  // namespace fs
